package cn.saltice.idempotence.idem.distributed;

import cn.saltice.idempotence.idem.exception.IdempotentException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.WriteRedisConnectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;
import cn.saltice.idempotence.idem.warpper.IdempotentRunnable;
import cn.saltice.idempotence.idem.warpper.IdempotentSupplier;

/**
 * @program: idempotence
 * @description: Redis
 * @author: Mr.Liu
 * @create: 2020-10-04 20:05
 **/
public class DistributedLockRedis implements DistributedLock{
    private static final Logger logger = LoggerFactory.getLogger(DistributedLockRedis.class);
    private RedissonClient redissonClient;
    /**
     * 降级时采用
     */
    private DistributedLockMysql distributedLockMysql;

    public DistributedLockRedis(RedissonClient redissonClient,DistributedLockMysql distributedLockMysql){
        this.redissonClient = redissonClient;
        this.distributedLockMysql = distributedLockMysql;
    }


    @Override
    public <T> T lock(String key, int waitTime, int leaseTime, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        return doLock(key,waitTime,leaseTime,TimeUnit.MILLISECONDS,success,fail);
    }

    @Override
    public <T> T lock(String key, int leaseTime, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        return doLock(key,0,leaseTime,TimeUnit.MILLISECONDS,success,fail);
    }

    @Override
    public <T> T lock(String key, int leaseTime, TimeUnit timeUnit, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        return doLock(key,0,leaseTime,timeUnit,success,fail);
    }

    @Override
    public void lock(String key, int waitTime, int leaseTime, IdempotentRunnable success, IdempotentRunnable fail) {
        doLock(key,waitTime,leaseTime,TimeUnit.MILLISECONDS,success,fail);
    }

    @Override
    public void lock(String key, int leaseTime, IdempotentRunnable success, IdempotentRunnable fail) {
        doLock(key,0,leaseTime,TimeUnit.MILLISECONDS,success,fail);
    }

    @Override
    public void lock(String key, int leaseTime, TimeUnit timeUnit, IdempotentRunnable success, IdempotentRunnable fail) {
        doLock(key,0,leaseTime,timeUnit,success,fail);
    }

    private <T> T doLock(String key, int waitTime, int leaseTime,TimeUnit timeUnit, IdempotentSupplier<T> success, IdempotentSupplier<T> fail){
        RLock rLock = null;
        try {
            try {
                rLock = redissonClient.getLock(key);
            }catch (Exception e){
                logger.error("get Redis Lock Error ",e);
                // 降级处理
                if(distributedLockMysql != null){
                    return  distributedLockMysql.lock(key, waitTime, leaseTime, success, fail);
                }
                return fail.get(key,new IdempotentException(e));
            }
            if (rLock == null){
                logger.debug("Redis Lock is null ");
                return fail.get(key,new IdempotentException("Redis Lock is null"));
            }
            boolean tryLock = false;
            try {
                tryLock = rLock.tryLock(waitTime,leaseTime,timeUnit);
            }catch (WriteRedisConnectionException e){
                logger.debug("lock error ",e);
                // 写入失败 ， 降级为数据库锁
                if(distributedLockMysql != null){
                    return distributedLockMysql.lock(key, waitTime, leaseTime, success, fail);
                }
            }catch (InterruptedException e){
                logger.debug("lock interrupted ",e);
                throw new IdempotentException(e);
            }
            if(!tryLock){
                return fail.get(key,null);
            }
            try {
                // 这个执行出错，直接抛异常了
                return success.get(key, null);
            } catch (Exception e){
                throw new IdempotentException(e);
            }
        }catch (Exception e){
            logger.debug("lock error ",e);
            throw new IdempotentException(e);
        }finally {
            if (rLock != null && rLock.isLocked() && rLock.isHeldByCurrentThread()){
                rLock.unlock();
            }
        }
    }

    private void doLock(String key, int waitTime, int leaseTime,TimeUnit timeUnit, IdempotentRunnable success, IdempotentRunnable fail){
        RLock rLock = null;
        try {
            try {
                rLock = redissonClient.getLock(key);
            }catch (Exception e){
                logger.error("get Redis Lock Error ",e);
                // 降级处理
                if(distributedLockMysql != null){
                    distributedLockMysql.lock(key, waitTime, leaseTime, success, fail);
                }else {
                    fail.run(key, new IdempotentException(e));
                }
            }
            if (rLock == null){
                logger.debug("Redis Lock is null ");
                fail.run(key, new IdempotentException("Redis Lock is null"));
                return;
            }
            boolean tryLock = false;
            try {
                tryLock = rLock.tryLock(waitTime,leaseTime,timeUnit);
            }catch (WriteRedisConnectionException e){
                logger.debug("lock error ",e);
                // 写入失败 ， 降级为数据库锁
                if(distributedLockMysql != null){
                    distributedLockMysql.lock(key, waitTime, leaseTime, success, fail);
                }
            }catch (InterruptedException e){
                logger.debug("lock interrupted ",e);
                throw new IdempotentException(e);
            }catch (NullPointerException e){
                logger.debug("get tryLock error ",e);
            }
            if(!tryLock){
                fail.run(key,null);
            }
            try {
                // 这个执行出错，直接抛异常了
                success.run(key,null);
            } catch (Exception e){
                throw new IdempotentException(e);
            }
        }catch (Exception e){
            logger.debug("lock error ",e);
            throw new IdempotentException(e);
        }finally {
            if (rLock != null && rLock.isLocked() && rLock.isHeldByCurrentThread()){
                rLock.unlock();
            }
        }
    }
}
